【忙しい人のための】Next.js公式チュートリアルを完走してきたので記事1本で振り返る【ギュッと凝縮】
本記事はNext.jsのチュートリアルが大きく変わったためリンク切れを起こしています。
技術メモのため記事としては残しますが、リンク切れにご留意ください。
また機会があれば新チュートリアルで記事を書こうと思いますm(_ _)m
こんちには。
データアナリティクス事業本部 インテグレーション部 機械学習チームの中村です。
今回は以下のNext.jsのチュートリアルをほぼ一通り(SEOのところ以外)実施しましたので、ポイントを記事化しました。
チュートリアル自体は、以下のような内容が分かるものとなっています。
- CRA(create-react-app)のみ使用しているとイメージしづらい、素のHTML + JavaScriptとReactの関係のイメージが分かる
- Reactがフレームワークではなくて、UIライブラリであるということが明確に理解できる
- Babelって何している?バンドラって何?などが分かる
- Next.jsを使うメリットが理解できる、どのようなときに有効なのか把握できる
以降はポイントのみを解説していきます。(私個人のコメントは緑枠で表記しています)
忙しい方やチュートリアルを一度やったことがある方の振り返りになれば幸いです。
Next.jsを触ったことがなくて、記事を読んで興味を持たれた方はぜひ、本編のチュートリアルの方にトライしてみてください。
なお、私自身はCRA(create-react-app)でアプリケーションを書いたことがありますが、Next.jsは勉強中という程度の人間となっています。
それでは見ていきましょう!
(始めにおことわりしておきますが、チュートリアルを1記事に納めているので結構長編です…)
FOUNDATIONS
Next.jsについて
Next.jsとは?
- Next.jsはReactフレームワーク
- Webアプリケーションの構成要素
- UI以外にもルーティング、データフェッチ
- その他レンダリングやサードパーティとの統合(CMS、認証支払いなど)
- Reactとは?
- Reactはフレームワークではなくライブラリであり、インタラクティブなUIを提供するライブラリ
- ライブラリとはUI構築に役立つ関数を提供するが、どこで使うかは開発者に任されているという意味
- それゆえに一からアプリケーションを構築するには労力が必要
- Next.jsはUI、ルーティング、データフェッチ、レンダリング、統合などの一般的なアプリケーション要件を解決
- これにより開発者とエンドユーザーの体験を向上させる
JavaScriptからReactへ
- レンダリングの仕組み
- サーバがHTMLをブラウザに返し、ブラウザはDOMを構築
- DOMとはHTMLの要素をオブジェクトとして表現したもので、HTMLとUIの仲介をするツリー構造をもつ
- 追加資料
JavaScriptでUIを更新する
- DOMはJavaScriptで操作可能なため、素のHTMLとDOMは異なる場合がある
- もちろん操作によりインタラクティブにDOMツリーを変更することも可能
<!-- index.html --> <html> <body> <div id="app"></div> <script type="text/javascript"> // Select the div element with 'app' id const app = document.getElementById('app'); // Create a new H1 element const header = document.createElement('h1'); // Create a new text node for the H1 element const headerContent = document.createTextNode( 'Develop. Preview. Ship. 🚀', ); // Append the text to the H1 element header.appendChild(headerContent); // Place the H1 element inside the div app.appendChild(header); </script> </body> </html>
- この方法の課題
- 冗長である。DOMをどのように変更するかユーザが記述する必要がある。(命令型的である)
- そのため見せたいものを記述するだけで、DOMの更新方法はコンピュータに任せる方法が発展(宣言的)
- 命令型と宣言型
- UI構築に関しては宣言型である方が好まれる
- DOMを操作するメソッドを書く代わりに、表示したいものを宣言する
- UIを構築するための宣言型ライブラリがReact
- 追加資料
React入門
- Reactを使う方法
- reactとreact-domという二つを
<script src="...">
で読み込むことで使える - DOMを直接操作する代わりに
ReactDOM.render()
メソッドを使用
- reactとreact-domという二つを
<!-- index.html --> <html> <body> <div id="app"></div> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script type="text/javascript"> const app = document.getElementById('app'); ReactDOM.render(<h1>Develop. Preview. Ship. 🚀</h1>, app); </script> </body> </html>
- ただしこのままではJavaScriptの部分でHTMLのタグが使用されているため、JavaScriptのSyntaxErrorとなる
- そのためにJSXが必要
- JSXとは
- JavaScriptの機能拡張で、HTMLのような構文でUIを記述可能
- ブラウザはJSXを理解できないため、JSXをJavaScriptに変換するためにBabelのようなコンパイラが必要
- Babelを使うフロー
- Babelを
<script src="...">
で読み込み <script type="text/javascript">
の代わりに<script type="text/jsx">
を使うことでBabelに変換対象であることを通知
- Babelを
<html> <body> <div id="app"></div> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- Babel Script --> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/jsx"> const app = document.getElementById('app'); ReactDOM.render(<h1>Develop. Preview. Ship. 🚀</h1>, app); </script> </body> </html>
Reactに必要なJavaScriptの知識
- Reactでよく使われる中核の表現として、いくつかJavaScriptの知識が必要でそのリソースが示されている
ここは今回あまり読んでおらず、解説としてもNext.js自体からは離れるため割愛します
React コアコンセプト
- 以下の3つ
- コンポーネント
- プロップス
- ステート
コンポーネントでUIを構築する
- Reactのコンポーネントについて
- コンポーネントにすることで保守性が高くなる。
- Reactのコンポーネントは単なるJavaScriptの関数であり、JSXをreturnする関数として作成
- ただし、HTMLやJavaScriptと区別するために大文字で記述し、HTMLタグと同じようにangle bracketsで囲む必要がある
- コンポーネントをDOMにレンダリングするには、
ReactDOM.render()
に第一引数として渡す
<script type="text/jsx"> const app = document.getElementById("app") function Header() { return (<h1>Develop. Preview. Ship. 🚀</h1>) } ReactDOM.render(<Header />, app) </script>
- コンポーネントは入れ子構造にできる
- ツリー構造を構築できる
ReactDOM.render()
にはトップのコンポーネントを渡せばよい
function Header() { return <h1>Develop. Preview. Ship. 🚀</h1>; } function HomePage() { return ( <div> <Header /> </div> ); } ReactDOM.render(<HomePage />, app);
プロップスでデータを表示する
- プロップスとは
- プロップスによりコンポーネントにプロパティを渡すことが可能
- プロップスは読み取り専用であるため、親から子への方向の一方通行のデータフロー
- プロップスはオブジェクトになるので、デストラクチャリングも可能
- JSXでプロップスなどの変数を使う方法
- JSX構文で中括弧{}で囲うことで可能
- 中括弧は「JSX Land」にいながら「JavaScript Land」に入る方法と考えることが可能
function HomePage() { return ( <div> <Header title="React 💙" /> </div> ); } function Header({ title }) { return <h1>title</h1>; }
- JavaScript式は何でも使える
- オブジェクトのプロパティ
- テンプレートリテラル
- 関数の戻り値
- 三項演算子
{ title ? title : 'default title'}
function Header({ title }) { return <h1>{title ? `Cool ${title}` : 'Default Title'}</h1>; }
- リストの反復
- mapを使って表現
- 要素にはkey propsを渡すよう警告がでる
- これは、Reactが配列のアイテムを一意に識別するための何かを必要とするためで、keyを与えるとDOMのどの要素を更新すべきかを知ることが可能
- 例えばアイテムIDのように一意であることが保証されたものを使うことを推奨
function HomePage() { const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton']; return ( <div> <Header title="Develop. Preview. Ship. 🚀" /> <ul> {names.map((name) => ( <li key={name}>{name}</li> ))} </ul> </div> ); }
ステートによる相互作用
- Reactではイベント名はキャメルケースとなる(onClick, onChange, onSubmitなど)
<button onClick={handleClick}>Likes ({likes})</button>
useState
フックによりステートをコンポーネントに追加- ステートとは通常はユーザのインタラクションによって引き起こされる部分と考える
- コンポーネントに渡されるプロップスと異なり、ステートはコンポーネント内で開始され保存する
- 子コンポーネントにプロップスとして状態情報を渡すことはできるが、更新のロジックはコンポーネント内に保持が必要
どうしても子コンポーネントに更新させたい場合、良い設計はさておきイベントハンドラとなる関数自体(以下でいうと`handleClick`)をプロップスで子コンポーネントに与えることで可能です。
function HomePage() { // ... const [likes, setLikes] = React.useState(0); function handleClick() { setLikes(likes + 1); } return ( <div> {/* ... */} <button onClick={handleClick}>Likes ({likes})</button> </div> ); }
likesはステートの値、setLikesはlikesを更新できる関数、useState(0)の0は初期値です。
onClickには関数オブジェクト自体(`handleCllick`)を渡す必要があり、関数オブジェクトを実行した状態(`handleClick()`)で渡すと無限ループになります(最初よくやるミス)。
handleClickをわざわざ定義したくない人は`onClick={ () => setLikes(likes + 1) }`とすればできないこともないです。
- その他ステートやデータフローの管理方法はまだ学ぶべきことが多くあるので、以下を読むことを推奨
- 追加資料
Reactの学習を継続する方法
- Reactについてのその他の重要なトピック
- React Documentationは貴重な参考資料であり、サンドボックスも含まれている以下がよい
- 次に、サンプルをReactからNext.jsに移行し、Next.jsの仕組みについて説明
- そしてより高度なNext.jsの機能を習得するためのWeb開発の概念を紹介
ReactからNext.jsへ
- ここまでの以下のようなサンプルコードをNext.jsに移行する手順について学ぶ
<html> <body> <div id="app"></div> <script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- Babel Script --> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/jsx"> const app = document.getElementById('app'); {/* ... */} ReactDOM.render(<HomePage />, app); </script> </body> </html>
Next.jsをはじめよう
- 空の
package.json
を作成
// package.json { }
- npmでreact, react-dom, nextをインストール
npm install react react-dom next
- これにより、index.htmlに対して不要なものを削除が可能となる
- index.htmlからreactとreact-domを読み込む
<script src="...">
を削除可能(npmでreactとreact-domはインストール済みなので) <html>
と<body>
はNext.jsが作るので不要- app要素と
ReactDOM.render
もNext.jsが管理するので不要 - JSXをJavaScriptにコンパイルするBabelも不要(Next.jsがJSXを変換するコンパイラを含むため)
- 同様にBabelを読み込む
<script src="...">
や変換対象であることを示す<script type="text/jsx">
も不要 - React.useStateがuseStateでOK
- index.htmlからreactとreact-domを読み込む
「React.useStateがuseStateでOK」はインポートの仕方によると考えています。
- index.htmlはJSXのみとなったためindex.jsxにリネーム
- index.jsxをpagesというフォルダに移動(Next.jsにとってpagesが重要な意味を持ちます)
- Next.jsがどのコンポーネントをメインコンポーネントとするか判断するために
export default
をindex.jsxのどこかに追記 - package.jsonに"next dev"を追加
- ここまででReactからNext.jsの以降できたため、以下で開発サーバの立ち上げが可能
- localhost:3000にアクセスすれば確認でき、Fast Refreshも可能に
npm run dev
ちなみにReactのアプリを一から構築する際に良く使用されるCRA(`create-react-app`)も、Next.jsなどのフレームワークと同様にReactアプリケーションの構築に必要な様々なものを準備することができます。
このCRAはフレームワークの台頭以前からあるものですが、その経緯と将来になどについて興味があれば以下を参照ください。
・Dan氏によるCreate React Appの将来、およびReactとフレームワークの関係性についてのコメントの翻訳 | Zenn
次のステップ
- 次のレッスンでは、関連するWeb開発の概念を紹介しながら、Next.jsがどのように機能するかを探る
Next.jsのしくみ
- 以下の内容を理解していく
- コードが実行される環境:開発環境と本番環境
- コードが実行されるとき:ビルドタイムとランタイムの比較
- レンダリングが行われる場所:クライアントとサーバー
開発環境から本番環境へ
- 開発環境と本番環境で最適化の方針が異なる
- 開発環境は開発者向けに最適化
- TypeScriptとESLintの統合、Fast Refreshなど、開発者の体験を向上させる
- 本番環境はエンドユーザーに最適化
- コードを変換して、パフォーマンスやアクセシビリティを向上させる
- 上記のため、開発環境を本番環境に移行するために多くのことが必要
- コンパイル(Compiling)、ミニファイ(Minifying)、バンドル(Bundling)、コード分割(Code Splitting)
- Next.jsは、これらの変換や基礎的なインフラストラクチャの多くを処理し、アプリケーションの本番稼働を容易にする
- これが可能なのは、Next.jsが低レベルプログラミング言語であるRustで書かれたコンパイラや様々な利用できるプラットフォームであるSWC(Speedy Web Compiler)を備えているため
コンパイル
- コンパイルとは
- JSX、TypeScript、JavaScriptのモダンバージョンなどの開発者に優しい言語をブラウザが理解できるJavaScriptに変換すること
- コンパイルは開発環境でも行われるが、本番環境のビルドの際にも行われる
ミニファイ
- ミニファイとは
- 開発者は、人間が読みやすいように最適化されたコードを書く
- このコードには通常、コメント、スペース、インデント、複数行など、実行には必要のない余分な情報が含まれる
- コードの機能を変えることなく、これらを削除するプロセスがミニファイ
- ファイルサイズを小さくすることで、アプリケーションのパフォーマンスを向上させることが目的
- Next.jsでは、JavaScriptとCSSのファイルが自動的にミニファイされ、本番に使用される
バンドル
- バンドルとは
- 開発者は、モジュール、コンポーネント、および関数に分割して、複数のファイルでより大きなアプリケーションを構築する
- バンドルとは、依存関係を解決し、ファイル(またはモジュール)をブラウザ用に最適化されたバンドルにマージするプロセス
- 具体的にはアプリケーションの依存関係グラフを解決し、ファイル数を減らす
- これにより、ユーザーがウェブページを訪れた際にファイルへのリクエスト数を減らすことを目的とする
コード分割
- コード分割とは
- 開発者は通常、アプリケーションを複数のページに分割し、異なるURLからアクセス可能とする(エントリポイントが複数となる)
- コード分割はこれを元にバンドルを、各エントリーポイントで必要な小さなチャンクに分割するプロセス
- Next.jsには、pages/の各ファイルは、ビルドステップにおいて、自動的に独自のJavaScriptバンドルにCode Splittingされる
- ページ間で共有されるコードは、別に分割され、再度同じコードがダウンロードされることを防ぐ
- 最初のロード後、次に移動する可能性のある他のページをプリロードし始める
- ダイナミックインポートは、最初に読み込まれるコードを手動で分割する別の方法
ビルドタイムとランタイム
- ビルドタイムとランタイム
- ランタイムはビルド後にユーザのリクエストに応えてアプリケーションが実行される期間のこと
- それより前のアプリケーションを本番用に準備する一連のステップがビルドタイム
クライアントとサーバー
- クライアントとサーバー
- Webアプリケーションの場合、クライアントはブラウザなどを指し、サーバーはデータセンターのコンピュータなどを指す
- クライアントはリクエストをサーバーに送信し、受け取った応答をユーザが操作可能なインターフェースに変換する
- サーバーはアプリケーションのコードを格納し、リクエストを受けて適切なレスポンスを返す
レンダリング
- レンダリングとは
- Reactで書いたコードをHTMLに変換する作業
- レンダリングはサーバーでもクライアントでも行われる
- 3種類の方式がある
- CSR : クライアントサイドレンダリング
- SSR : サーバーサイドレンダリング
- SSG : 静的サイト生成
- サーバーサイドレンダリングと静的サイト生成は、クライアントへ送信前にHTML変換がされるため、プリレンダリングともいう
- CSR : クライアントサイドレンダリング
- 空のHTMLとUI構築のためのJavaScriptが送られ、最初のレンダリングをユーザのデバイス側で行う
- 標準的なReactのアプリケーションはこちらとなる
- プリレンダリング
- Next.jsではデフォルトで全ページをプリレンダリングする(サーバー上でHTMLを作成する)
- これにより、CSRのレンダリング中のようにユーザに白紙が表示される、などが発生しないことがメリット
- SSR : サーバーサイドレンダリング
- リクエストごとにHTMLがサーバー上で生成されて送付される
- サーバーからは生成されたHTML、JSONデータ、インタラクティブなJavaScriptの命令がクライアントに送付される
- クライアントでは、HTMLを使用して高速にページを表示し、ReactがJSONデータとJavaScriptの命令によりインタラクティブな動作を実現する(このプロセスをハイドレーションと呼ぶ)
- Next.jsでは、getServerSidePropsを使うことで、サーバーサイドでページをレンダリングすることが可能
- 詳細は「Data Fetching: getServerSideProps | Next.js」参照
- SSG : 静的サイト生成
- 実行時にはサーバーは存在せず、デプロイ時にビルドタイムで一度生成され、HTMLはCDNに保存され、リクエストごとに再利用する
- Next.jsでは、getStaticPropsを使うことで、静的にページを生成する
- 詳細は「Data Fetching: getStaticProps | Next.js」参照
- 使い分け
- Next.jsではページ単位でこれらのレンダリング方法を選択可能
- 特定のユースケースに適したレンダリング方法については、データフェッチのドキュメントを参照する
- 詳細は「Data Fetching: Overview | Next.js」参照
CDNとエッジ
- ネットワークの構成
- アプリケーションのコードは、オリジン、CDN、エッジなどに分散
- オリジンサーバー
- オリジンサーバーはアプリケーションのオリジナル版を保存
- オリジンサーバーはリクエストを受信すると計算を行ってレスポンスを送信するが、結果作業の結果はCDNに移動させることが可能
- CDN(Content Delivery Network)
- クライアントとオリジンサーバー間に配置し、最も近いCDN拠点がユーザにレスポンスすることが可能
- これによりオリジンサーバーの負荷が軽減され、コンテンツの配信も高速化される
- エッジ
- エッジは概念でありCDNもエッジの一部である
- エッジサーバーはコードの断片の実行をすることも可能
- つまりコンテンツのキャッシュも、コードの実行もユーザに近いところで行うことが可能
- Next.jsでは、Middlewareを使ってEdgeでコードを実行することができ、React Server Componentsを使えばすぐに実行することもできる
- 詳細は「Advanced Features: Middleware | Next.js」、「React 18: Overview | Next.js」参照
CREATE YOUR FIRST APP
ここからは、Next.jsのサンプルプロジェクトを用いた説明となっています。
DevelopersIOのブログ記事でもやってみた記事が詳しく掲載されています。
・公式チュートリアルでNext.jsに入門してみた (1) 〜アプリ新規作成、ページ遷移、スタイリング編〜 | DevelopersIO
こちらのシリーズでかなり詳しく記載されているので、ここでは要点を箇条書きにするのにとどめます。詳細は上記のブログも併せてごらんください。
Create a Next.js App
- ReactでWebアプリケーションをゼロから構築するために考慮が必要な点
- コードはwebpackのようなバンドルラーで束ね、Babelのようなコンパイラで変換する必要がある
- コード分割など本番の最適化を行う必要がある
- パフォーマンスやSEOのために、一部のページを静的にプリレンダリングしたい場合がある
- また、サーバーサイドレンダリングやクライアントサイドレンダリングを使用したい場合がある
- Reactアプリをデータストアに接続するために、サーバーサイドのコードを書かなければならない場合がある
- Next.jsのフレームワークを使うことでこれらのような問題を解決できる
- 直感的なページベースのルーティングシステム(ダイナミックルートにも対応)
- プリレンダリングは、静的生成(SSG)とサーバーサイドレンダリング(SSR)の両方をページ単位でサポート
- 自動コード分割によるページロードの高速化
- 最適化されたプリフェッチによるクライアントサイドルーティング
- CSSと Sassを内蔵し、あらゆるCSS-in-JSライブラリをサポート
- Fast Refreshに対応した開発環境
- サーバーレスファンクションでAPIエンドポイントを構築するAPIルート
- フルエクステンダブル
セットアップ
- create-next-appでテンプレートをGitHubから取得して構築
Welcome to Next.js
- 開発サーバーにアクセスできる
ページの編集
pages/index.js
を編集して、画面がFast Refreshで更新されることを確認
Next.jsにおけるPages
- Next.jsでは、pages配下のパスでルートと自動的に関連付けされる
pages/index.js
が`http://localhost:3000/`のルートに関連付けられる- 同様に
pages/posts/first-post.js
はhttp://localhost:3000/posts/first-postに関連付けられる
- pages配下で
.js
ファイルからexport default
されたReactコンポーネントをページという- もちろん、ReactコンポーネントなのでJSXを
return
する必要がある
- もちろん、ReactコンポーネントなのでJSXを
- HTMLやPHPファイルを使ってWebサイトを構築するのと同じようなもの
- HTMLを書く代わりにJSXを書き、Reactコンポーネントを使用するイメージ
Linkコンポーネント
- Next.jsでは、さまざまなコンポーネントがあらかじめ準備されており、
<Link>
もその一つ <Link>
は<a>
タグの代わりに使うイメージで、hrefというプロパティは同じ
クライアントサイドナビゲーション
- クライアントサイドナビゲーションとは
- リンクによりクライアントサイドナビゲーションが可能に
- JavaScriptを使ってページ遷移を行うこと
- ブラウザのページ遷移よりも高速に動作
<Link>
ではなく<a>
タグを使う場合はフルリフレッシュとなる
- コード分割とプリフェッチ
- 前提としてNext.jsではコード分割を自動で行い、各ページで必要なものだけを読み込む
- 本番ビルドでは、Linkコンポーネントがブラウザのビューポートに表示されると自動的にバックグラウンドでリンク先をプリフェッチする
- これによりページ遷移が瞬時に機能する
- Linkコンポーネントについては、next/linkのAPIリファレンスを参照
- ルーティング全般については、ルーティングのドキュメントで詳しく解説
Assets, Metadata, CSS
- Next.jsには、CSSと Sassが組み込まれているが、ここではCSSを使用
- ここでは以下を学ぶ
- Next.jsに静的ファイル(画像など)を追加する方法
- 各ページの
<head>
の中身をカスタマイズする方法 - CSS Modulesを使用してスタイリングされた再利用可能なReactコンポーネントを作成する方法
pages/_app.js
で グローバルCSSを追加する方法- Next.jsでスタイリングするための便利なTipsの紹介
Assets
- 静的Asset
- Next.jsでは
public
配下に画像などの静的Assetを配置することが可能 public
のファイルはpages
と同様にアプリケーションのルートから参照可能- publicディレクトリは、
robots.txt
、Google Site Verification、およびその他の静的資産にも便利 - 静的ファイルについては「Basic Features: Static File Serving | Next.js」も参照
- Next.jsでは
- Imageコンポーネント
<img>
タグの代わりに使用することで様々な機能を提供- 異なる画面サイズでの画像の応答性を確保すること
- サードパーティ製のツールやライブラリで画像を最適化する
- ビューポートに入ったときだけ画像を読み込む
- 特徴の詳細
- ビルド時ではなくユーザーからのリクエストに応じてオンデマンドで画像を最適化するため、画像が増えてもビルド時間は増加しない
- 画像はデフォルトで遅延ロードされるため、画面外の画像によって読み込み速度が低下したりが発生しない
- Googleが検索ランキングに利用しようとしているCumulative Layout Shiftを回避するように常に描画することが可能
- heightとwidthのPropsはソース画像と同じアスペクト比で設定が必要
- 詳しくは「next/image | Next.js」を参照
Metadata
- Headコンポーネント
<head>
タグの代わりにこのコンポーネントを使う<head>
はもともとページのタイトルとか付けるタグ- 詳しくは「next/head | Next.js」参照
- lang属性を追加するなど、
<html>
タグをカスタマイズしたい場合は、pages/_document.js
ファイルを作成することで可能 - 詳しくは「Advanced Features: Custom
Document
| Next.js」参照
Third-Party JavaScript
- Scriptコンポーネント
- 分析、広告、カスタマーサポートウィジェットなど、ゼロから記述する必要のない新しい機能をサイトに導入するために使用
<script>
タグの代わりにこのコンポーネントを使うことができる<script>
タグを使うと、同じページで取得される他のJavaScriptコードに対して、いつロードされるかを明確に把握することができなくなる
- Scriptコンポーネントにすると、制御が可能なように拡張することができる
- strategy="lazyOnload"により遅延読み込みが可能
- onLoadにイベントハンドラを設定することで、読み込み後の処理を指定可能
CSSスタイリング
- stylesというフォルダにCSSファイルが置かれる
- CSS Modules
- 自動的にユニークなクラス名を作成することで、コンポーネントレベルでCSSをローカルにスコープすることが可能
- 詳細は「CSS Styling - Assets, Metadata, and CSS | Learn Next.js」参照
- Next.jsアプリケーションは、それ以外にも以下のようなさまざまな方法でスタイルを設定することが可能
.css
や.scss
ファイルを取り込ことができるSass。- Tailwind CSSのようなPostCSSライブラリ。
- styled-jsx、styled-components、emotionなどのCSS in-JS ライブラリ
CSS Modulesの使用方法
- CSS Modulesを使用することでユニークなクラス名が自動生成し、名前衝突を避けてCSSの利用が可能
- CSS Modulesを使用するためには、
.module.css
というsuffixを持つファイルを作成し、そこにCSSを記述する - Reactコンポーネント特有のスタイルであれば、同じ階層に
.module.css
ファイルを作成するcomponents/layout.js
に対して、components/layout.module.css
を作成
- 以下のようにインポートして使用
import styles from './layout.module.css'; export default function Layout({ children }) { return <div className={styles.container}>{children}</div>; }
- またNext.jsのコード分割機能は、CSS Modulesの
.module.css
ファイルに対しても機能する - これにより、各ページで読み込まれるCSSを最小限にし、バンドルサイズを小さくることが可能
- 詳細は以下も参考
- ちなみに
components
フォルダは、共通で使用するReactコンポーネントの置き場として採用されることが多い
グローバルスタイル
- グローバルスタイルとは
- CSS Modulesは、コンポーネントレベルのスタイルに便利
- その反面、あるCSSをすべてのページで読み込むようにしたい場合はグローバルスタイルを使用する
- グローバルスタイルの使用方法
pages/_app.js
というファイルを作成- この
_app.js
のexport default
は、アプリケーション内のすべてのページをラップするトップレベルのReactコンポーネントとなっている - このReactコンポーネントを使用して、ページ間を移動するときに状態を保持したり、ここで行うようなグローバルスタイルを記述することが可能
_app.js
の詳細は「Advanced Features: CustomApp
| Next.js」を参照_app.js
を追加した場合は開発サーバを再起動する必要がある_app.js
からのみグローバルなCSSファイルのインポートが可能で、それ以外の場所でグローバルCSSをインポートすることはできない- 詳細は「https://nextjs.org/docs/basic-features/built-in-css-support#adding-a-global-stylesheet」参照
- グローバルなCSSファイルはどこにでも置けて、どんな名前でも使用可能
- 以下が最終的な
pages/_app.js
の記載となる
// `pages/_app.js` import '../styles/global.css'; export default function App({ Component, pageProps }) { return <Component {...pageProps} />; }
このサンプルプロジェクトですが、`globals.css`ではなく`global.css`であることに注意が必要です(`globals.css`はもともとプロジェクト内にありますが汗)
Polishing Layout
この章はレイアウトの磨き上げの章で、様々な修正が入っている
- 複数コンポーネントで使いまわすCSSは、
styles/utils.module.css
などとしてユーティリティクラスを書く- ユーティリティクラスは、メソッドではなくCSSセレクタを記述するアプローチを指す
- 詳しくはUtility firstなTailwindCSSなどのドキュメント「Utility-First Fundamentals - Tailwind CSS」を参照
- その他の捕捉
og:image
のようなメタタグの使用。メタタグについては「https://en.wikipedia.org/wiki/Meta_element」を参照<header>
タグや<main>
タグの仕様- Imageコンポーネントに対するpriorityの使用(https://nextjs.org/docs/api-reference/next/image#priority)
Reactコンポーネントに変数名だけのPropsを記述すると、boolean扱いとなる
Styling Tips
- clsxを使用したクラス切り替えが可能
- lukeed/clsx: A tiny (228B) utility for constructing
className
strings conditionally. - classNameをclsx経由で与えることにより、スタイルをPropsなどにより切り替えられる
- lukeed/clsx: A tiny (228B) utility for constructing
- PostCSSを設定なしで追加可能
npm install -D tailwindcss autoprefixer postcss
の実行postcss.config.js
の作成tailwind.config.js
でcontentオプションを指定し、コンテンツソースを設定することを推奨- Content Configuration - Tailwind CSS
- 詳細は「Advanced Features: Customizing PostCSS Config | Next.js」も参照
- Next.js公式のTailwindCSSのサンプルは「next.js/examples/with-tailwindcss at canary · vercel/next.js」を参照
- Sassの使用
npm install -D sass
で簡単に使用可能
- その他のリファレンス
プリレンダリングとデータフェッチ
プリレンダリング
- プリレンダリングはNext.jsで最も重要な概念の一つ
- プリレンダリングは、クライアントサイドのJavaScriptですべてを行うのではなく、各ページのHTMLをあらかじめ生成することを意味
- 生成された各HTMLは、そのページに必要な最小限のJavaScriptコードと関連付けられる
- ブラウザでページが読み込まれると、そのJavaScriptコードが実行され、ページが完全にインタラクティブに(これをハイドレーションという)
- アプリケーションがプレーンなReactである場合、プリレンダリングがないため、JavaScriptを無効にするとアプリが表示されない
プリレンダリングの2つの形態
- 静的サイト生成(SSG)とサーバーサイドレンダリング(SSR)
- https://nextjs.org/docs/basic-features/pages#static-generation-recommended
- https://nextjs.org/docs/basic-features/pages#server-side-rendering
- 静的サイト生成は、ビルド時にHTMLを生成するプリレンダリング方式
- サーバーサイドレンダリングは、リクエストごとにHTMLを生成するプリレンダリング方式
- Next.jsでは各ページでどのプリレンダリング形式を使うかを選択可能
- どちらを採用するか
- 基本的には、静的サイト生成を可能な限り使用することが推奨
- 静的サイト生成とした場合、CDNで提供することができるので、リクエストごとにサーバー側でレンダリングするよりもはるかに高速に
- 使い分けとしては、ユーザリクエストに先行してページをプリレンダリングすることが可能ならば、静的サイト生成を選択すべき
- リクエストの都度、ページが変化する場合はサーバーサイドレンダリングを使用する
まとめると以下のような形かと思います。
・CSR : 更新が頻繁なケース(カウンタなどの簡単なUI操作などを想定)
・SSR : リクエストごとに更新が必要なケース
・SSG : それ以外
データありの静的サイト生成とデータなしの静的サイト生成
- データありの静的サイト生成
- ページによっては、まず外部データを取得しないとHTMLをレンダリングできない場合がある
- ビルド時にファイルシステムへのアクセス、外部APIの取得、データベースへの問い合わせが必要な場合がある
- Next.jsはこのようなデータありの静的サイト生成もサポート
- ページ単位のReactコンポーネントのexportに、getStaticPropsを書くことで実現する
- getStaticPropsにより、Next.jsに対して「このページにはいくつかのデータ依存性があるので、ビルド時にプリレンダリングするときは、最初にそれらを解決するように」伝えることが可能
- 詳細は「https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation」も参照
ブログデータの準備
- この章は、サンプルプログラム向けのデータ準備について説明
- gray-matterでmarkdownを読み込み、それをデータとして使用
- fsは、ファイルシステムからファイルを読み込むためのNode.jsモジュール
- pathは、ファイルパスを操作するためのNode.jsモジュール
- matterは、各マークダウンファイルのメタデータを解析するためのライブラリ
- サンプルでは
lib
フォルダを作成して、ファイルからの読み込み処理を実装しているが、任意のフォルダ名が使用可能で、こういったモジュールはlib
やutils
を使うケースが多い
getStaticPropsの実装
- getStaticPropsで、
return { props: { ... } }
とすれば、コンポーネント側のPropsでデータを受け取ることが可能
export async function getStaticProps() { // ... return { props: { data }, }; } export default function Home ({ data }) { // ... }
getStaticPropsの詳細
- getStaticPropsでは、外部APIからの取得やデータベースへの問い合わせも可能
- これはgetStaticPropsがサーバーサイドでのみ実行されるため
- つまり、ブラウザに送信されることなく、直接データベースへの問い合わせのようなコードを書くことが可能
- 開発環境と本番環境の比較
- 開発サーバー(
npm run dev
などで起動する)では、リクエストごとにgetStaticPropsが実行される - 本番環境では、getStaticPropsはビルド時に実行される
- この本番環境での動作はgetStaticPathsが返すフォールバックキーを使って強化することが可能
- 詳細「https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-false」
- ビルド時に実行することを前提としているため、クエリパラメータやHTTPヘッダなど、リクエスト時にしか利用できないデータを利用することはできない
- 開発サーバー(
pages/
内でのみ許可される- getStaticPropsは、
pages/
配下からしか書き出せず、pages/
以外にあるファイルに書くことはできません - この制限の理由の1つは、Reactはページがレンダリングされる前に必要なデータをすべて持っている必要があるため
- getStaticPropsは、
- リクエスト時にデータを取得する必要がある場合
- getStaticPropsは構築時に一度だけ行われるため、頻繁に更新されるデータやユーザーのリクエストごとに変更されるデータには適さない
- この場合はサーバーサイドレンダリング、またはクライアントサイドレンダリングを使用する
リクエスト時のデータ取得
- サーバーサイドレンダリングを使用する方法
- getStaticPropsの代わりに、getServerSidePropsを使う
- getServerSidePropsはリクエスト固有のパラメータをcontextで渡すことが可能
export async function getServerSideProps(context) { return { props: { // props for your component }, }; }
- クライアントサイドレンダリングを使う方法もある
- データをプリレンダリングする必要がない場合はこちらの戦略もあり
- 外部データを必要としないページの部分を静的に生成(プリレンダリング)する
- ページが読み込まれたら、JavaScriptを使ってクライアントから外部データを取得し、残りの部分に入力する
- このアプローチは、たとえばユーザーのダッシュボードページで有効
- ダッシュボードはプライベートなユーザー専用ページなので、SEO対策も無関係で、ページもプリレンダリングが不要
- データは頻繁に更新されるため、リクエストタイムのデータフェッチが必要
- SWR
- Next.jsの開発チームではSWRというデータフェッチ用のReactフックを作成している
- https://swr.vercel.app/
- クライアントサイドでデータをフェッチする場合は、これを強く推奨
- キャッシュ、再バリデーション、フォーカストラッキング、インターバルでのリフェッチなど、さまざまな処理を行うことが可能
import useSWR from 'swr'; function Profile() { const { data, error } = useSWR('/api/user', fetch); if (error) return <div>failed to load</div>; if (!data) return <div>loading...</div>; return <div>hello {data.name}!</div>; }
SWR以外の選択肢として、react-queryなどもあるので、ここはプロジェクトに応じて好みのものを使えば良いと考えられます。
ダイナミックルート
- 以下について学ぶ
- getStaticPathsを使用して、動的なルートを持つページを静的に生成する方法
- 各ブログ記事のデータを取得するgetStaticPropsの書き方
- remarkを使ってマークダウンをレンダリングする方法
- 動的経路を持つページへのリンク方法
- ダイナミックルートに関するTIPS
ページパスが外部データに依存するケース
- Next.jsでは、外部データに依存するパスを持つページを静的に生成することができる
- 実現するステップは以下
pages/posts/
配下に[id].js
というページ(ファイル)を作成[id].js
にはこれまでと同様にページのコンポーネントを記述- 加えてその
[id].js
内でgetStaticPathsをエクスポートし、[id]
に入る可能性のあるリストをオブジェクトで返す必要がある - getStaticPropsでは、これらをparamsから取得できる
getStaticPathsの実装
- 以下のような形式となる
- getStaticPathsは単なる文字列のリストではなく、オブジェクトの配列である必要がある
- またその各要素のオブジェクトはparamsをキーに持ち、paramsに更にidをキーに持つオブジェクトを含む必要がある
- getStaticPropsでは、返したオブジェクトの配列の各要素に
params.id
という形でアクセス可能
// pages/posts/[id].js export default function Post() { return <Layout>...</Layout>; } export async function getStaticPaths() { // Return a list of possible value for id // ex. // const paths = [ { params: { id: 'ssg-ssr' } }, { params: { id: 'pre-rendering' } } ] // return {paths, fallback: false} } export async function getStaticProps({ params }) { // Fetch necessary data for the blog post using params.id }
getStaticPropsの実装
特に再掲するようなポイントがないため詳細を略します。
マークダウンのレンダリング
- remarkというライブラリを使用している
- remarkjs/remark: remark is a popular tool that transforms markdown with plugins
- remarkはawaitを使う必要があるので、getPostDataをasyncに修正
- そもそもasync awaitの詳細は「async function - JavaScript | MDN」を参照
Postページの磨き上げ
- dateのフォーマットにdate-fnsというライブラリを使用
細かいコードや、CSSスタイルやレイアウト調整については詳細は割愛します。
Indexページの磨き上げ
こちらも特に挙げるようなポイントがないため詳細を略します。
ダイナミックルートの詳細
- getStaticPathsはgetStaticPropsと同様に任意のデータソースからフェッチが可能
- 開発環境と本番環境の比較
- 開発サーバー(
npm run dev
などで起動する)では、リクエストごとにgetStaticPathsが実行される - 本番環境では、getStaticPathsはビルド時に実行される
- 開発サーバー(
- fallbackの詳細
- 今回は
fallback: false
としたため、getStaticPathsにないパスにアクセスすると404となる fallback: true
と設定すると、404とならずにページのfallbackバージョンを提供するfallback: blocking
と設定すると、新しいパスはgetStaticPropsでサーバーサイドレンダリングされ、将来のリクエストのためにキャッシュされる- 詳しくは「https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-false」を参照
- 今回は
- キャッチオールルート
- ダイナミックルートは、括弧の中に3つのドット(たとえば今回の場合は
pages/posts/[...id].js
)を追加することで、すべてのパスをキャッチするように拡張可能 - この場合、getStaticPathsが返す形式も多少変更が必要
- キャッチオールルートの詳細は「https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes」も参照
- ダイナミックルートは、括弧の中に3つのドット(たとえば今回の場合は
export async function getStaticPaths() { return [ { params: { // Statically Generates /posts/a/b/c id: ['a', 'b', 'c'], }, }, //... ]; } export async function getStaticProps({ params }) { // params.id will be like ['a', 'b', 'c'] }
- ルーター
- Next.jsのルーターにアクセスしたい場合は、next/routerからuseRouterフックをインポートする
- 詳しくは「https://nextjs.org/docs/api-reference/next/router」を参照
- 404ページ
- カスタム404ページを作成するには、
pages/404.js
を作成する - 詳しくは「https://nextjs.org/docs/advanced-features/custom-error-page#404-page」を参照
- カスタム404ページを作成するには、
- その他にもgetStaticPropsとgetStaticPathsを使用する様々な例がある
API Routes
- Next.jsはAPI Routesをサポートしており、Node.jsのサーバーレス関数としてAPIエンドポイントを簡単に作成することが可能
APIルーティングの作成
- APIルートは、Next.jsアプリの中にAPIエンドポイントを作成できる
pages/api/
ディレクトリ内に、以下のような関数を作ることで実現
// pages/api/hello.js export default function handler(req, res) { res.status(200).json({ text: 'Hello' }); }
- これらは、Serverless Functions(Lambdasとも呼ばれる)としてデプロイすることが可能
- 上記を実装し、 http://localhost:3000/api/helloにアクセスすれば`{"text": "hello"}`というレスポンスが得られる
req
はhttp.IncomingMessage
のインスタンスであるreq
にはいくつかの組み込み済みのMiddrewares(現在は、Request Helpersの様子)が含まているres
はhttp.ServerResponseのインスタンスであるreq
にはいくつかのヘルパー関数も含まれている
APIルート詳細
- getStaticPropsやgetStaticPathsからAPI Routesにfetchするべきではない
- API Routes代わりに、サーバーサイドのコードをgetStaticPropsまたはgetStaticPathsに直接記述する
- 詳しくは「https://nextjs.org/docs/basic-features/data-fetching/get-static-props#write-server-side-code-directly」を参照
- API Routesの優れたユースケースの一つは、フォーム入力の処理
- API RoutesにPOSTリクエストを送信させることが可能
- そして、それを直接データベースに保存するコードを書くことも可能
- API Routeのコードはクライアント側にバンドルに含まれないので、安全にサーバーサイドのコードを書くことが可能
- プレビューモード
- 静的サイト生成は、ページがヘッドレスCMSからデータをフェッチする場合に便利です。
- しかしヘッドレスCMSで原稿を書いていて、その原稿をすぐにページでプレビューしたいときには、理想的ではない
- このような問題を解決するために、Next.jsにはAPI Routesを利用したプレビューモードという機能がある
- 詳細は「Advanced Features: Preview Mode | Next.js」を参照
- 動的なAPIルーティング
- APIルートは、通常のページと同じように、ダイナミックルートで動作させることが可能
- 詳細は「API Routes: Dynamic API Routes | Next.js」を参照
Next.jsアプリをデプロイする
- 学ぶこと
- VercelにNext.jsをデプロイする方法
- DPSワークフロー(Develop, Preview, and Ship)
- Next.jsアプリを独自のホスティングプロバイダーにデプロイする方法
こちらの章はここは読むだけにとどめています。
Push to GitHub
特に挙げるようなポイントがないため詳細を略します。
Deploy to Vercel
特に挙げるようなポイントがないため詳細を略します。
Next.jsとVercel
- VercelはNext.jsのクリエイターによって作られており、Next.jsを第一級にサポート
- Next.jsアプリをVercelにデプロイすると、デフォルトで以下のような機能が使用できる
- 静的サイト生成やアセット(JS、CSS、画像、フォントなど)を使用するページは、自動的にVercel Edge Networkから提供され、高速に配信できる
- サーバーサイドレンダリングとAPI Routesを使用するページは、自動的に分離されたServerless Functionsとなり、ページのレンダリングやAPIリクエストは無限に拡張される
- カスタムドメインの使用
- Vercelにデプロイすると、Next.jsアプリにカスタムドメインを割り当てることが可能に
- Domains Overview | Vercel Docs
- 環境変数
- 環境変数を設定することも可能
- Configuring a Build | Vercel Docs
- HTTPS対応
- HTTPSはデフォルトで有効になっており(カスタムドメインを含む)、余分な設定は不要で、SSL証明書は自動更新される
- DPS(Develop, Preview, and Ship)が推奨のワークフロー
- Develop
- Next.jsでコードを書き、Next.jsの開発サーバーを起動して、そのホットリロード機能を利用
- Preview
- GitHub上のブランチに変更をプッシュし、Vercelがプレビューデプロイメントを作成し、URL経由で利用
- このプレビューURLを他の人と共有し、フィードバックを得ることが可能
- コードレビューに加え、デプロイメントプレビューを行うことが可能
- Ship
- 本番に出荷するために、プルリクエストをmainにマージする
その他のホスティング
- 独自のホスティングの手順
- ホスティングプロバイダーで
npm run build
を行う - ビルド後
npm run start
でサーバーを起動
- ホスティングプロバイダーで
次のおすすめのステップ
- TypeScriptを使う
- TypeScript | Learn Next.js
- デモアプリ自体をTypeScriptとする手順は「 https://nextjs.org/learn/excel/typescript」参照
- 次に学ぶべきこと
まとめ
いかがでしたでしょうか。本記事がNext.jsを使われる方の参考やきっかけになれば幸いです。